//
// Created by user on 20-3-16.
//

#ifndef _MEMORY_H_
#define _MEMORY_H_

#include "../GeneralLibrary/Printf.h"
#include "../GeneralLibrary/Lib.h"

/*===========================  宏定义变量区  ===========================*/
// 页表项的大小
#define PTE_SIZE    512

// 内核层线性起始地址
#define KERNEL_START_LINEAR_ADDRESS    ((unsigned long)0xffff800000000000)

//#define PAGE_GDT_SHIFT	39

#define PAGE_GDT_SHIFT    39

// 1G页面的位数
#define PAGE_1G_SHIFT    30

// 2M页面的位数
#define PAGE_2M_SHIFT    21

// 4K页面的位数
#define PAGE_4K_SHIFT    12

// 2M页面的大小，这里就是直接从 1 左移指定位数就行了
#define PAGE_2M_SIZE    (1UL << PAGE_2M_SHIFT)

// 4K页面的大小，这里也是直接从 1 左移指定位数
#define PAGE_4K_SIZE    (1UL << PAGE_4K_SHIFT)

// 2M页面的屏蔽码，通常用于屏蔽低于2M的数值，这里就是把 减一取反得来
#define PAGE_2M_MASK_CODE    (~ (PAGE_2M_SIZE - 1))

// 4K页面的屏蔽码，通常用于屏蔽低于4K的数值
#define PAGE_4K_MASK_CODE    (~ (PAGE_4K_SIZE - 1))

#define PG_Referenced    (1 << 2)

/*======================== 页表属性 =======================*/

// 第0位：物理页是否存在
#define    PAGE_Present    (1UL << 0)

// 第1位：读写操作标志位
#define    PAGE_R_W    (1UL << 1)

// 第2位：访问模式标志位
#define	PAGE_U_S	(1UL << 2)

// 第3位：页级写穿标志位
#define PAGE_PWT    (1UL << 3)

// 第4位：页级禁用缓存禁用缓存
#define PAGE_PCD    (1UL << 4)

// 第5位：访问标志位
#define	PAGE_Accessed	(1UL << 5)

// 第6位：脏页标志位
#define	PAGE_Dirty	(1UL << 6)

// 第7位：页面属性表标志位
#define    PAGE_PS        (1UL << 7)

// 第8位：全局页标志位
#define	PAGE_Global	(1UL << 8)

// 第12位：标志是否存在下一级页表
#define	PAGE_PAT	(1UL << 12)

// 第63位：执行禁用标志位，这个是IA-32e新增的
#define PAGE_XD		(1UL << 63)

#define PAGE_KERNEL_GDT        (PAGE_R_W | PAGE_Present)

#define PAGE_KERNEL_Dir        (PAGE_R_W | PAGE_Present)

#define    PAGE_KERNEL_Page    (PAGE_PS  | PAGE_R_W | PAGE_Present)

//1,0
#define PAGE_USER_GDT		(PAGE_U_S | PAGE_R_W | PAGE_Present)

//2,1,0
#define PAGE_USER_Dir		(PAGE_U_S | PAGE_R_W | PAGE_Present)

//7,2,1,0
#define	PAGE_USER_Page		(PAGE_PS  | PAGE_U_S | PAGE_R_W | PAGE_Present)

/*====================== 页属性 =======================*/
#define PG_K_Share_To_U    (1 << 8)

// 置位：已在页表中映射，复位：未在页表中映射
#define PG_PTable_Maped    (1 << 0)

// 置位：内核初始化程序，复位：非内核初始化程序
#define PG_Kernel_Init    (1 << 1)

// 置位：使用中的页，复位：未使用的页
#define PG_Active    (1 << 4)

// 置位：内核层地址空间，复位：应用层地址空间
#define PG_Kernel    (1 << 3)

// 置位：设备寄存器/内存，复位：物理内存空间
#define PG_Device    (1 << 2)

// 置位：已被共享的内存页，复位：未被共享的内存页
#define PG_Shared    (1 << 4)

// 这个是指专门为I/O留下的 0-16M的内存空间
#define ZONE_DMA    (1 << 0)

// 这个是指 1G 内存空间
#define ZONE_NORMAL    (1 << 1)

// 这个是指大于 1G 的内存空间
#define ZONE_UNMAPED    (1 << 2)

/*===========================  宏定义函数区  ===========================*/

// 2M页面上边界对齐，上边界是因为内存分配的地点是递增的，对齐的意思是，分配的地址必须是 2M 的整数倍
#define PAGE_2M_ALIGN(addr)    (((unsigned long)(addr) + PAGE_2M_SIZE - 1) & PAGE_2M_MASK_CODE)

// 4K页面上边界对齐
#define PAGE_4K_ALIGN(addr)    (((unsigned long)(addr) + PAGE_4K_SIZE - 1) & PAGE_4K_MASK_CODE)

// 虚拟地址转物理地址
#define Virtual_To_Physical(addr)    ((unsigned long)(addr) - KERNEL_START_LINEAR_ADDRESS)

// 物理地址转虚拟地址
#define Physical_To_Virtual(addr)    ((unsigned long *)((unsigned long)(addr) + KERNEL_START_LINEAR_ADDRESS))

// 虚拟地址转 2M 页面
#define Virtual_To_2M_Page(kaddr)    (memoryManagementStruct.pPageArray + (Virtual_To_Physical(kaddr) >> PAGE_2M_SHIFT))
#define Physical_to_2M_Page(kaddr)    (memoryManagementStruct.pPageArray + ((unsigned long)(kaddr) >> PAGE_2M_SHIFT))

// 将指定的 size 按照 long 型边界对齐
#define SIZEOF_LONG_ALIGN(size) ((size + sizeof(long) - 1) & ~(sizeof(long) - 1) )

// 设置 PLM4 的属性和值
#define    mk_mpl4t(addr, attr)    ((unsigned long)(addr) | (unsigned long)(attr))
#define set_mpl4t(mpl4tptr, mpl4tval)    (*(mpl4tptr) = (mpl4tval))

// 设置 PDP 的属性和值
#define mk_pdpt(addr, attr)    ((unsigned long)(addr) | (unsigned long)(attr))
#define set_pdpt(pdptptr, pdptval)    (*(pdptptr) = (pdptval))

// 设置 PDT 的属性和设置新的页表值
#define mk_pdt(addr, attr)    ((unsigned long)(addr) | (unsigned long)(attr))
#define set_pdt(pdtptr, pdtval)        (*(pdtptr) = (pdtval))

// 通过强制更新CR3寄存器，来让TLB刷新
#define flushTLB()                        \
do                                \
{                                \
    unsigned long    tmpreg;                    \
    __asm__ __volatile__    (                \
                "movq	%%cr3,	%0	\n\t"    \
                "movq	%0,	%%cr3	\n\t"    \
                :"=r"(tmpreg)            \
                :                \
                :"memory"            \
                );                \
}while(0)

/*===========================  结构体重名区  ===========================*/

typedef struct E820Struct E820;
typedef struct ZoneStruct Zone;
typedef struct PageStruct Page;
typedef struct GlobalMemoryDescriptorStruct GlobalMemoryDescriptor;

/*===========================  结构体定义区  ===========================*/

// E820是CPU的内存结构(物理内存段结构体)， __attribute__((packed)) 是说不需要对齐，采用紧凑模式编译
struct E820Struct {

    unsigned long address;      // 地址
    unsigned long length;       // 长度
    unsigned int type;          // 类型

}__attribute__((packed));

// 区域空间结构体(可用物理内存段结构体)
struct ZoneStruct {

    Page *pPageArray;                 // 指向本物理内存段的页数组
    unsigned long pageArrayLength;    // 页数组的长度

    unsigned long startAddress;       // 本段的起始地址
    unsigned long endAddress;         // 本段的结束地址
    unsigned long length;             // 本段的长度
    unsigned long attribute;          // 本段的属性

    GlobalMemoryDescriptor *gmd;      // 指向全局内存描述符

    unsigned long pageUsingCount;     // 已使用物理内存页数量
    unsigned long pageFreeCount;      // 空闲物理内存页数量

    unsigned long totalPagesLink;     // 物理页被引用次数，这里是因为一个物理页可以映射到不同程序，有这种需求
};

// 物理内存页结构体
struct PageStruct {

    Zone *pZone;                      // 指向本页所属物理内存段
    unsigned long physicalAddress;    // 本页的物理地址
    unsigned long attribute;          // 本页的属性
    unsigned long referenceCount;     // 本页被引用的次数
    unsigned long createTime;         // 本页的创建时间

};

// 全局内存描述符结构体
struct GlobalMemoryDescriptorStruct {

    E820 e820[32];                        // 物理内存段结构数组
    unsigned long e820Length;             // 物理内存段结构数组长度，从0开始

    unsigned long *pBitsMap;              // 物理地址空间页映射位图
    unsigned long bitsCount;              // 物理地址空间页数量
    unsigned long bitsLength;             // 物理地址空间页映射位图长度

    Page *pPageArray;                     // 指向全局 pageArray 数组的指针
    unsigned long pageTotalCount;         // page 在内存中的总数
    unsigned long pageArrayLength;        // pageArray 的长度

    Zone *pZoneArray;                     // 指向全局 Zone 数组的指针
    unsigned long zoneTotalCount;         // zone 在内存中的总数
    unsigned long zoneArrayLength;        // zoneArray 的长度

    unsigned long kernelStartCode;        // 内核程序的起始代码段地址
    unsigned long kernelEndCode;          // 内核程序的结束代码段地址
    unsigned long kernelEndData;          // 内核程序的结束数据段地址
    unsigned long kernelEndBrk;           // 内核程序的结束地址

    unsigned long endOfStruct;            // 内存页管理结构的结尾地址

};

extern GlobalMemoryDescriptor memoryManagementStruct;

extern unsigned long *globalCR3;

/*===========================  函数声明区  ===========================*/

/**
 * 初始化内存
 */
void initMemory();

/**
 * 初始化页表
 */
void pageTableInit();

/**
 * 获取到CR3
 * @return 返回获取到的CR3
 */
unsigned long *getCR3();

/*==================================== 页面相关的函数 ===================================*/

/**
 * 初始化指定页面
 * @param page 页
 * @param flags 页的属性
 */
unsigned long pageInit(Page *page, unsigned long flags);

/**
 * 清除页面
 * @param pPage 要清除的页面
 * @return 成功返回 1 ，失败返回 0
 */
unsigned long pageClean(Page *pPage);

/**
 * 获取页面的属性
 * @param pPage 页面指针
 * @return 成功返回 1，失败返回 0
 */
unsigned long getPageAttribute(Page *pPage);

/**
 * 设置页面的属性
 * @param pPage 页面指针
 * @param flags 要设置的页面属性
 * @return 成功返回 1，失败返回 0
 */
unsigned long setPageAttribute(Page *pPage, unsigned long flags);

/**
 * 将可用的物理内存页分配出去
 * @param zoneSelect 选择要在哪个区域里面分配内存
 * @param number
 * @param pageFlags
 * @return
 */
Page *allocPages(int zoneSelect, int number, unsigned long pageFlags);

/**
 * 释放页面
 * @param pPage 要释放的页面的指针
 * @param number 释放的数量
 */
void freePages(Page *pPage, int number);

#endif //_MEMORY_H_
